home *** CD-ROM | disk | FTP | other *** search
/ Collection of Internet / Collection of Internet.iso / infosrvr / dev / scott / WWW / NextStep / Implementation / HyperText.m < prev    next >
Text File  |  1993-07-06  |  49KB  |  1,713 lines

  1. //    HyperText Implementation                    HyperText.m
  2. //    ------------------------
  3. //
  4. // HyperText is like Text, but includes links to and from other hypertexts.
  5. //
  6. // Authors:
  7. //    TBL        Tim Berners-Lee CERN/CN
  8. //
  9. // See also:
  10. //    The HTAnchor class, and the HyperAccess class.
  11. //
  12. // History:
  13. //    25 Sep 90    Written (TBL) with the  help of the Interface builder.
  14. //    14 Mar 91    Page width is taken from application's page layout.
  15.  
  16.  
  17. // Notes.
  18. //
  19. //    For all anchors to have addresses, the node must be made with
  20. //    newAnchor:Server: . Do not use any other creation methods inherited.
  21.  
  22. #import <appkit/appkit.h>
  23. #import "HyperText.h"
  24. #import "HyperManager.h"
  25. #import "HTUtils.h"
  26. #import "HTParse.h"
  27. #import "HTStyle.h"
  28.  
  29. #import "HTAccess.h"
  30.  
  31.  
  32. @implementation HyperText
  33.  
  34.  
  35. #define DEFAULT_TITLE "World-Wide Web Document"
  36.  
  37. #define ANCHOR_ID_PREFIX 'z'    /* Auto anchors run z1, z2, ... 921122 */
  38.  
  39. extern        HTStyleSheet * styleSheet;    /* see StyleToy */
  40.  
  41. static int window_sequence = 0;        /* For stacking windows neatly */
  42. #define SLOTS    30            /* Number of position slots */
  43. static HyperText * slot[SLOTS];            /* Ids of HT objects taking them */
  44.  
  45.  
  46. #define NICE_HEIGHT 600.0    /* Allows a few windows to be stacked */
  47. #define MAX_HEIGHT 720.0    /* Worth going bigger to get it all in    */
  48. #define MIN_HEIGHT 80.0        /* Mustn't lose the window! */
  49. #define MIN_WIDTH 200.0
  50.  
  51. static HyperText * HT;        /* Global pointer to self to allow C mixing */
  52. +initialize
  53. {
  54.     int i;
  55.     for (i=0; i<SLOTS; i++) slot[i] = 0;
  56.     return [super initialize];
  57. }
  58.  
  59. //    Get Application page layout's Page Width
  60. //    ----------------------------------------
  61. //
  62. //    Returned in pixels
  63. //
  64. static float page_width()
  65. {
  66.     PrintInfo * pi = [NXApp printInfo];            // Page layout details
  67.     NXCoord topMargin, bottomMargin, leftMargin, rightMargin;
  68.     const NXRect * paper = [pi paperRect];        //    In points
  69.     
  70.     [pi getMarginLeft:&leftMargin right:&rightMargin
  71.                 top:&topMargin bottom:&bottomMargin];    /* In points */
  72.     return (paper->size.width - leftMargin - rightMargin);
  73.  
  74. }
  75.  
  76. /*    Update the document and window according to the anchor info
  77. */
  78.  
  79. - syncWithAnchor
  80. {
  81.     CONST char * title = HTAnchor_title(nodeAnchor);
  82.     if (title) [window setTitle:title];
  83.     else {
  84.         char * addr = HTAnchor_address((HTAnchor*)nodeAnchor);
  85.     [window setTitle:addr];
  86.     free(addr);
  87.     }
  88.     
  89.     [self setEditable: HTList_indexOf(HTAnchor_methods(nodeAnchor),
  90.                     HTAtom_for("PUT")) != (-1) ? YES : NO];
  91.     return self;
  92. }
  93.  
  94. //            Class methods
  95. //            -------------
  96. //
  97.  
  98.  
  99. //    Build a HyperText GIVEN its nodeAnchor.
  100. //    --------------------------------------
  101. //
  102. //
  103.  
  104. + newAnchor:(HTParentAnchor *)anAnchor
  105. {
  106.     NXRect aFrame = {{0.0, 0.0}, {page_width(), NICE_HEIGHT}};
  107.     self = [super newFrame:&aFrame];
  108.     if (TRACE) printf("New node\n");
  109.  
  110.     nextAnchorNumber = 0;
  111.     protection = 0;        // Can do anything
  112.     format = WWW_HTML;        // By default
  113.     [self setMonoFont: NO];    // By default
  114.     theRuns->chunk.growby = 16 * sizeof(theRuns->runs[0]); //  efficiency
  115.  
  116.     [self setDelegate: TheManager];            /* For changes */
  117.     
  118.     nodeAnchor= anAnchor;
  119.     HTAnchor_setDocument(anAnchor, (void*) self);
  120.     [self syncWithAnchor];
  121.     return self;
  122. }
  123.  
  124. //            Instance Methods
  125. //            ----------------
  126.  
  127.  
  128. //    Free the hypertext.
  129.  
  130. - free
  131. {
  132.     slot[slotNumber] = 0;    //    Allow slot to be reused
  133.     HTAnchor_setDocument(nodeAnchor, NULL);
  134.     return [super free];
  135. }
  136.  
  137.  
  138. //    Read and set format
  139.  
  140. - (int) format         {return format; }
  141. - setFormat:(int)f     {format = f; return self; }
  142.  
  143.  
  144. //    Useful diagnostic routine:  Dump to standard output
  145. //    ---------------------------------------------------
  146. //
  147. //    This first lists the runs up to and including the current run,
  148. //    then it lists the attributes of the current run.
  149. //
  150. - dump:sender
  151. {
  152.     int pos;                /* Start of run being scanned */
  153.     int sob=0;                /* Start of text block being scanned */
  154.     NXRun * r = theRuns->runs;
  155.     NXTextBlock * block = firstTextBlock;
  156.     
  157.     printf("Hypertext %p, selected(%i,%i)", self, sp0.cp, spN.cp);
  158.     if (delegate) printf(", has delegate");
  159.     printf(".\n");
  160.     
  161.     printf("    Frame is at (%f, %f, size is (%f, %f)\n",
  162.         frame.origin.x, frame.origin.y,
  163.         frame.size.width, frame.size.height);
  164.     
  165.     printf("    Text blocks and runs up to character %i:\n", sp0.cp);
  166.     for (pos = 0; pos<=sp0.cp; pos = pos+((r++)->chars)) {
  167.     while(sob <= pos) {
  168.         printf("%5i: Block of %i/%i characters at 0x%x starts `%10.10s'\n",
  169.             sob, block->chars,  malloc_size(block->text), block->text,
  170.         block->text);
  171.         sob = sob + block->chars;
  172.         block = block->next;
  173.     }
  174.     printf("%5i: %3i of fnt=%i p=%i gy=%3.2f RGB=%i i=%i fl=%x\n",
  175.         pos, r->chars, (int)r->font, r->paraStyle,
  176.         r->textGray,r->textRGBColor, (int)(r->info),
  177.         *(int*)&(r->rFlags));
  178.  
  179.     }
  180.     r--;    /* Point to run for start of selection */
  181.  
  182.     printf("\n    Current run:\n\tFont name:\t%s\n", [r->font name]);
  183.     {
  184.         NXTextStyle *p = (NXTextStyle *)r->paraStyle;
  185.     if (!p) {
  186.         printf("\tNo paragraph style!\n");
  187.     } else {
  188.         int tab;
  189.         printf("\tParagraph style %i\n", p);
  190.         printf("\tIndents: first=%f, left=%f\n",
  191.             p->indent1st, p->indent2nd);
  192.         printf("\tAlignment type=%i, %i tabs:\n",
  193.             p->alignment, p->numTabs);
  194.         for (tab=0; tab<p->numTabs; tab++) {
  195.             printf("\t    Tab kind=%i at %f\n",
  196.             p->tabs[tab].kind, p->tabs[tab].x);
  197.         }
  198.     }
  199.     }
  200.     printf("\n");
  201.     return self;
  202. }
  203.  
  204.  
  205. //    Adjust Scrollers and Window size for current text size
  206. //    ------------------------------------------------------
  207. //
  208. //The scrollers are turned off if they possibly can be, to simplify the screen.
  209. // If the text is editable, they have to be left on, although formatted text is
  210. // allowed to wrap round, and so horizontal scroll bars are not necessary.
  211. // The window size is adjusted as a function of the text size and scrollers.
  212. //
  213. //    @@ Bug: The resize bar should be removed if there are no scrollers.
  214. //    This is difficult to do -- might have to make a new window.
  215. //
  216. - adjustWindow
  217. {
  218. #define MAX_WIDTH  paperWidth
  219.  
  220.     NXRect scroll_frame;
  221.     NXRect old_scroll_frame;
  222.     NXSize size;
  223.     BOOL scroll_X, scroll_Y;            // Do we need scrollers?
  224.     
  225.     ScrollView* scrollview = [window contentView];// Pick up id of ScrollView
  226.     float paperWidth = page_width();        // Get page layout width
  227.     
  228.     [self syncWithAnchor];
  229.     [window disableFlushWindow];    // Prevent flashes
  230.     
  231.     [self setVertResizable:YES];    // Can change size automatically
  232.     [self setHorizResizable:tFlags.monoFont];
  233.     [self calcLine];            // Wrap text to current text size
  234.     [self sizeToFit];            // Reduce size if possible.
  235.     
  236.     if (maxY > MAX_HEIGHT) {
  237.         scroll_Y = YES;
  238.         size.height = NICE_HEIGHT;
  239.     } else {
  240.         scroll_Y = [self isEditable];
  241.     size.height = maxY < MIN_HEIGHT ? MIN_HEIGHT : maxY;
  242.     }
  243.  
  244.     if (tFlags.monoFont) {
  245.         scroll_X = [self isEditable] || (maxX>MAX_WIDTH);
  246.     [self setNoWrap];
  247.     } else {
  248.         scroll_X = NO;
  249.         [self setCharWrap:NO];                // Word wrap please 
  250.     }
  251.     if (maxX > MAX_WIDTH) {
  252.         size.width = MAX_WIDTH;
  253.     } else {
  254.         size.width = maxX < MIN_WIDTH ? MIN_WIDTH : maxX;
  255.     }
  256.  
  257. // maxX is the length of the longest line.
  258. //    It only represnts the width of the page
  259. //     needed if the line is quad left. If the longest line was
  260. //    centered or flush right, it may be truncated unless we resize
  261. //    it to fit.
  262.  
  263.     if (!scroll_X) {
  264.         [self sizeTo:size.width:maxY];
  265.     [self calcLine];
  266.     [self sizeToFit];        // Algorithm found by trial and error.
  267.     }
  268.  
  269. //    Set up the scroll view and window to match:
  270.  
  271.     [ScrollView getFrameSize:&scroll_frame.size
  272.                 forContentSize: &size
  273.             horizScroller:    scroll_X
  274.             vertScroller:    scroll_Y
  275.             borderType:     NX_LINE];
  276.             
  277.     [scrollview setVertScrollerRequired:scroll_Y];
  278.     [scrollview setHorizScrollerRequired:scroll_X];
  279.  
  280. //    Has the frame size changed?
  281.  
  282.     [scrollview getFrame:&old_scroll_frame];
  283.     if ( (old_scroll_frame.size.width != scroll_frame.size.width)
  284.        ||(old_scroll_frame.size.height != scroll_frame.size.height)) {
  285.  
  286.                 
  287. // Now we want to leave the top left corner of the window unmoved:
  288.  
  289. #ifdef OLD_METHOD    
  290.         NXRect oldframe;
  291.     [window getFrame:&oldframe];
  292.         [window sizeWindow:scroll_frame.size.width:scroll_frame.size.height];
  293.     [window moveTopLeftTo: oldframe.origin.x
  294.                  : oldframe.origin.y + oldframe.size.height];
  295. #else
  296.     NXRect newFrame;
  297.     scroll_frame.origin.x = 150 + (slotNumber % 10)   * 30
  298.                     + ((slotNumber/10)%3)* 40;
  299.     scroll_frame.origin.y = 185 + NICE_HEIGHT - scroll_frame.size.height
  300.                     - (slotNumber % 10)   * 20
  301.                     - ((slotNumber/10)%3)*  3;
  302.     [Window getFrameRect:&newFrame
  303.         forContentRect:&scroll_frame
  304.         style:NX_TITLEDSTYLE];    // Doesn't allow space for resize bar
  305.     newFrame.origin.y = newFrame.origin.y - 9.0;
  306.     newFrame.size.height = newFrame.size.height + 9.0; // For resize bar
  307.     [window placeWindow:&newFrame];    
  308. #endif
  309.     }
  310.     
  311. #ifdef VERSION_1_STRANGENESS
  312. //    In version 2, the format of the last run is overwritten with the format
  313. //    of the preceding run!
  314.     {
  315.       NXRect frm;        /* Try this to get over "text strangeness" */
  316.       [self getFrame:&frm];      
  317.       [self renewRuns:NULL text:NULL frame:&frm tag:0];
  318.     }
  319. #endif
  320.     [window reenableFlushWindow];
  321.     [self calcLine];        /* Prevent messy screen */
  322.     [window display];        /* Ought to clean it up */
  323.  
  324.     return self;
  325.     
  326. } /* adjustWindow */
  327.  
  328.  
  329. //    Set up a window in the current application for this hypertext
  330. //    -------------------------------------------------------------
  331.  
  332. - setupWindow
  333. {
  334.     NXRect scroll_frame;                // Calculated later
  335.     NXSize min_size = {300.0,200.0};            // Minimum size of text
  336.     NXSize max_size = {1.0e30,1.0e30};            // Maximum size of text
  337.    
  338.     ScrollView * scrollview;
  339.     NXSize nice_size = { 0.0, NICE_HEIGHT };        // Guess height
  340.         
  341.     nice_size.width = page_width();
  342.     [ScrollView getFrameSize:&scroll_frame.size
  343.                 forContentSize: &nice_size
  344.             horizScroller:NO
  345.             vertScroller:YES
  346.             borderType: NX_LINE];
  347.  
  348.     {
  349.         int i;    /* Slot address */
  350.     for(i=0; (i<SLOTS) && slot[i]; i++) ;    /* Find spare slot */
  351.     if (i=SLOTS) i = (window_sequence = (window_sequence+1) % SLOTS);
  352.     slot[i] = self;
  353.     slotNumber = i;
  354.     scroll_frame.origin.x = 150 + (slotNumber % 10)   * 30
  355.                     + ((slotNumber/10)%3)* 40;
  356.     scroll_frame.origin.y = 185 - (slotNumber % 10)   * 20
  357.                     - ((slotNumber/10)%3)*  3;
  358.      }
  359.      
  360.  //    Build a window around the text in order to display it.
  361.  
  362. #define NX_ALLBUTTONS 7  // Fudge -- the followin methos is obsolete in 3.0:    
  363.     window = [Window newContent:        &scroll_frame
  364.                     style:        NX_TITLEDSTYLE
  365.                 backing:     NX_BUFFERED
  366.                 buttonMask:    NX_ALLBUTTONS
  367.                 defer:        NO];        // display now                
  368.     [window setDelegate:self];            // Get closure warning
  369.     [window makeKeyAndOrderFront:self];        // Make it visible
  370.     [window setBackgroundGray: 1.0];        // White seems to be necessary.
  371.     
  372.     scrollview = [ScrollView newFrame:&scroll_frame];
  373.     [scrollview setVertScrollerRequired:YES];
  374.     [scrollview setHorizScrollerRequired:NO];        // Guess.
  375.     [[window setContentView:scrollview] free];     // Free old view, size new one.
  376.  
  377.                         
  378.     [scrollview setDocView:self];
  379.     [self setOpaque:YES];            // Suggested in the book
  380.     [self setVertResizable:YES];        // Changes size automatically
  381.     [self setHorizResizable:NO];
  382.     [self setMinSize:&min_size];        // Stop it shrinking to nought
  383.     [self setMaxSize:&max_size];        // Stop it being chopped when editing
  384.     [self notifyAncestorWhenFrameChanged: YES]; // Tell scrollview See QA 555
  385.     [window display];                // Maybe we will see it now
  386.     return self;
  387. }
  388.  
  389.  
  390. //        Return Instance Variables
  391. //        -------------------------
  392.  
  393. - (HTParentAnchor*) nodeAnchor    { return nodeAnchor; }
  394.  
  395.  
  396. /*    Return reference to a part of, or all of, this node
  397. **    ---------------------------------------------------
  398. */
  399.  
  400. //    Generate an anchor for a given part of this node, giving it an
  401. //    arbitrary (numeric) name.
  402.  
  403. - (char *) new_unique_name
  404. {
  405.     static    char s[20];
  406.  
  407.     sprintf(s,"%c%i",ANCHOR_ID_PREFIX, nextAnchorNumber++);
  408.     [delegate textDidChange:self];
  409.     return s;
  410. }
  411.  
  412.  
  413. //    Check whether an anchor has been selected
  414. //    -----------------------------------------
  415.  
  416. - (HTChildAnchor *) anchorSelected
  417. {
  418.     int sor;
  419.     NXRun *r, *s, *e;        /* Scan, Start and end runs */
  420.     HTChildAnchor * a;
  421.     
  422.     
  423.     for (sor = 0, s=theRuns->runs;
  424.         sor+s->chars<=sp0.cp;
  425.         sor = sor+((s++)->chars)) ;
  426.     for (e=s; sor+e->chars<spN.cp; sor = sor+ (e++)->chars);
  427.     for(r=s; r<=e; r++) {
  428.     if (a= (HTChildAnchor *)r->info) return a;
  429.     }
  430.     if (TRACE) printf("HyperText: No anchor selected.\n");
  431.     return NULL;
  432. }
  433.  
  434.  
  435. //    Public method:    Generate an anchor for the selected text
  436. //    --------------------------------------------------------
  437. //
  438. //    If the document is not editable, then nil is returned
  439. //    (unless the user asks for an existing anchor).
  440. //
  441. - (HTChildAnchor *) referenceSelected
  442. {
  443.     HTChildAnchor * a;
  444.     HTStyle * style = HTStyleNew();
  445.     
  446.     a = [self anchorSelected];
  447.     if (a) return a;            /* User asked for existing one */
  448.     
  449.     if ([self isEditable]) [window setDocEdited:YES];
  450.     else return NULL;
  451.  
  452.     a = HTAnchor_findChildAndLink(nodeAnchor,
  453.         [self new_unique_name], NULL, NULL);
  454.     style->anchor = a;
  455.     [self applyStyle:style];
  456.     if (TRACE) printf("HyperText: New dest anchor %i from %i to %i.\n",
  457.         a, sp0.cp, spN.cp);
  458.     [delegate textDidChange:self];
  459.     return a;
  460. }
  461.  
  462. //    Generate a live anchor for the text, and link it to a given one
  463. //    ----------------------------------------------------------------
  464.  
  465. - (HTChildAnchor *) linkSelTo:(HTAnchor *)anAnchor
  466. {
  467.     HTChildAnchor * a;
  468.     HTStyle * style = HTStyleNew();
  469.     
  470.     if (!anAnchor) return NULL;            /* Anchor must exist */
  471.     
  472.     if ([self isEditable]) [window setDocEdited:YES];
  473.     else return NULL;
  474.  
  475.     a = [self anchorSelected];
  476.     if (!a) {
  477.     a = HTAnchor_findChildAndLink(nodeAnchor, [self new_unique_name],
  478.         NULL, NULL);
  479.         if (TRACE) printf("HyperText: New source anchor %i from %i to %i.\n",
  480.              a, sp0.cp, spN.cp);
  481.     } else {
  482.     [self selectAnchor:a];
  483.         if (TRACE) printf("HyperText: Existing source anchor %i selected.\n",a);
  484.     }
  485.     style->anchor = a;
  486.     HTAnchor_link((HTAnchor*) a, anAnchor, 0);    // Link it up
  487.     [self applyStyle:style];        // Will highlight it because linked
  488.     free(style);
  489.     return a;
  490. }
  491.  
  492.  
  493. //    Purge anchor from selected text
  494. //    -------------------------------
  495. //
  496. //    The anchor is left becuase in general we don't delete anchors.
  497. //    In any case, we would have to check whether all text referencing it
  498. //    was deleted.
  499. //
  500. - unlinkSelection
  501. {
  502.     HTStyle * style = HTStyleNew();
  503.     
  504.     if ([self isEditable]) [window setDocEdited:YES];
  505.     else return nil;
  506.     
  507.     style->anchor = CLEAR_POINTER;
  508.     [self applyStyle:style];
  509.     free(style);
  510.     return self;
  511. }
  512.  
  513.  
  514. - (HTParentAnchor *) referenceAll
  515. {
  516.     return nodeAnchor;    // Just return the same one each time
  517.     
  518. }
  519.  
  520.  
  521. //    Select an anchor
  522. //    ----------------
  523. //
  524. //    If there are any runs linked to this anchor, we select them. Otherwise,
  525. //    we just bring the window to the front.
  526.  
  527. - selectAnchor:(HTChildAnchor *)anchor
  528. {
  529.     NXRun *s, *e;    /* run for start, run for end */
  530.     int start, sor;
  531.     NXRun * limit = (NXRun *)((char *)theRuns->runs + theRuns->chunk.used);
  532.     for (sor=0, s=theRuns->runs; s<limit; sor = sor+(s++)->chars) {
  533.         if (s->info == (void *)anchor){
  534.         start = sor;
  535.         
  536.             for (e=s; (e < limit)
  537.                 &&((e+1)->info == (void*)anchor);
  538.             sor = sor+(e++)->chars);
  539.             [window makeKeyAndOrderFront: self];
  540.         [self setSel:start:sor+e->chars];
  541.         return [self scrollSelToVisible];
  542.         }
  543.     }
  544.     if (TRACE) printf("HT: Anchor has no explicitly related text.\n");
  545.     return [window makeKeyAndOrderFront: self];
  546. }
  547.  
  548.  
  549. //
  550.  
  551. //    Return selected link (if any)                selectedLink:
  552. //    -----------------------------
  553. //
  554. //    This implementation scans down the list of anchors to find the first one
  555. //    on the list which is at least partially selected.
  556. //
  557.  
  558. - (HTChildAnchor*) selectedLink
  559. {
  560.  
  561.     int sor;            /* Start of run */
  562.     NXRun *r, *s, *e;        /* Scan, Start and end runs */
  563.     HTChildAnchor * a;
  564.     int startPos, endPos;
  565.     
  566.     for (sor = 0, s=theRuns->runs;
  567.         sor+s->chars<=sp0.cp;
  568.         sor = sor+((s++)->chars)) ;
  569.     startPos = sor;            /* Start of s */
  570.     for (e=s; sor+e->chars<spN.cp; sor = sor+ (e++)->chars);
  571.     for(r=s, a=NULL; r<=e; startPos = startPos + (r++)->chars) {
  572.     a = (HTChildAnchor *)r->info;
  573.     if (a) break;
  574.     }
  575.  
  576.     if (!a) {
  577.         if (TRACE) printf("HyperText: No anchor selected.\n");
  578.     return NULL;
  579.     }
  580.     
  581. //    Extend/reduce selection to entire anchor
  582.  
  583.     {
  584.     endPos = startPos + r->chars;
  585.      for (s=r; (HTChildAnchor *)((s-1)->info) == a; s--)
  586.         startPos = startPos-(s-1)->chars;
  587.     for (e=r; (HTChildAnchor *)((e+1)->info) == a; e++)
  588.         endPos   =   endPos+(e+1)->chars;
  589.     [self setSel:startPos:endPos];
  590.     }
  591.     return a;
  592. }
  593.  
  594. ///    Follow selected link (if any)                followLink:
  595. //    -----------------------------
  596. //
  597.  
  598. //    Find selected link and follow it
  599.  
  600. - (HTChildAnchor*)followLink
  601. {
  602.     HTChildAnchor * a = [self selectedLink];
  603.  
  604.     if (!a) return a;        // No link selected
  605.    
  606.     if (HTLoadAnchor(HTAnchor_followMainLink((HTAnchor*)a)))
  607.          return a; // Try to follow link
  608.     
  609.     if (TRACE) printf("HyperText: Can't follow anchor.\n");
  610.     return a;            // ... but we did highlight it.
  611. }
  612.  
  613. - setTitle:(const char *)aString
  614. {
  615.     return [window setTitle:aString];
  616. }
  617.  
  618.  
  619. //                STYLE METHODS
  620. //                =============
  621. //
  622.  
  623. //    Find Unstyled Text
  624. //    ------------------
  625. //
  626. // We have to check whether the paragraph style for each run is one
  627. // on the style sheet.
  628. //
  629. - selectUnstyled: (HTStyleSheet *)sheet
  630. {
  631.     NXRun * r = theRuns->runs;
  632.     int sor;
  633.     for (sor=0; sor<textLength; r++) {
  634.         if (!HTStyleForParagraph(sheet, r->paraStyle)) {
  635.         [self setSel:sor:sor+r->chars];    /* Select unstyled run */
  636.         return self;
  637.     }
  638.         sor = sor+r->chars;
  639.     }
  640.     return nil;
  641. }
  642.  
  643.  
  644. //    Copy a style into a run
  645. //    -----------------------
  646. static void apply(HTStyle * style, NXRun * r)
  647. {
  648.     if (style->font) {
  649.     r->font = style->font;
  650.     }
  651.     if (style->paragraph) {
  652.     r->paraStyle = style->paragraph;
  653.     }
  654.     if (style->anchor) {
  655.     r->info = (style->anchor == CLEAR_POINTER) ? 0 : (style->anchor);
  656.     }
  657.     
  658.     
  659.     if (style->textGray>=0)
  660.         r->textGray = style->textGray;
  661.     
  662.     r->rFlags.underline = NO;
  663.     if (r->info) {
  664. //        r->textGray = 0.166666666;        /* Slightly grey - horrid */
  665.         if (HTAnchor_followMainLink((HTAnchor *)(r->info))) {
  666. //        r->textGray = NX_DKGRAY;    /* Anchor highlighting */
  667.         r->rFlags.underline = YES;
  668.     }
  669.     }
  670.     r->rFlags.dummy = (r->info != 0);        /* Keep track for typingRun */
  671.     
  672.     if (style->textRGBColor>=0)
  673.         r->textRGBColor = style->textRGBColor;
  674. }
  675.  
  676.  
  677. //    Check whether copying a style into a run will change it
  678. //    -------------------------------------------------------
  679.  
  680. static BOOL willChange(HTStyle * style, NXRun *r)
  681. {
  682.     if (r->font != style->font) return YES;
  683.  
  684.     if (style->textRGBColor>=0)
  685.         if (r->textRGBColor != style->textRGBColor) return YES;
  686.  
  687.     if (style->textGray>=0)
  688.         if (r->textGray != style->textGray) return YES;
  689.  
  690.     if (style->paragraph) {
  691.         if (r->paraStyle != style->paragraph) return YES;
  692.     }
  693.     if (style->anchor) {
  694.         if (r->info != style->anchor) return YES;
  695.     }
  696.     return NO;
  697. }
  698.  
  699. //    Update a style
  700. //    --------------
  701. //
  702. //
  703. - updateStyle:(HTStyle *)style
  704. {
  705.     NXRun * r = theRuns->runs;
  706.     int sor;
  707.     for (sor=0; sor<textLength; r++) {
  708.         if (r->paraStyle == style->paragraph) 
  709.         apply(style, r);
  710.         sor = sor+r->chars;
  711.     }
  712.     [self calcLine];
  713.     [window display];
  714.     return nil;
  715. }
  716.  
  717.  
  718. //    Delete an anchor from this node, without freeing it.
  719. //    ----------------
  720. //
  721. //
  722. - disconnectAnchor:(HTChildAnchor *)anchor
  723. {
  724.     NXRun * r = theRuns->runs;
  725.     int sor;
  726.     for (sor=0; sor<textLength; r++) {
  727.         if (r->info == (void *)anchor) 
  728.         r->info = 0;
  729.         r->textGray =  NX_BLACK;
  730.         sor = sor+r->chars;
  731.     }
  732.     [window display];
  733.     return nil;
  734. }
  735.  
  736.  
  737. //    Find start of paragraph
  738. //    -----------------------
  739. //
  740. //    Returns the position of the character after the newline, or 0.
  741. //
  742. - (int) startOfParagraph: (int) pos
  743. {
  744.     NXTextBlock * block;
  745.     int sob;
  746.     unsigned char * p;
  747.     for(block=firstTextBlock, sob=0; sob+block->chars <=pos; block=block->next)
  748.         sob = sob + block->chars;
  749.     for(p=block->text+(pos-sob)-1; p >= block->text; p--)
  750.         if (*p == '\n') return sob + (p - block->text) +1 ;/* Position of newline */
  751.     while (block->prior) {
  752.         block = block->prior;
  753.     sob = sob - block->chars;
  754.         for(p=block->text+(block->chars-1); p>=block->text; p--)
  755.             if (*p == '\n') return sob + (p-block->text)+1;/* Position of newline */
  756.     }
  757.     return 0;
  758. }
  759.  
  760.  
  761. //    Find end of paragraph
  762. //    -----------------------
  763. //
  764. //    Returns the position after the newline, or the length of the text.
  765. //    Note that any number of trailing newline characters are included in
  766. //    this paragraph .. basically because the text object does not support
  767. //    the concept of space after or before paragraphs, so extra paragrpah
  768. //    marks must be used.
  769. //
  770. - (int) endOfParagraph: (int) pos
  771. {
  772.     NXTextBlock * block;
  773.     int sob;
  774.     unsigned char * p;
  775.     BOOL found_newline = NO;
  776.     
  777.     if (pos>=textLength) return textLength;
  778.     
  779.     for(block=firstTextBlock, sob=0; sob+block->chars <=pos; block=block->next)
  780.         sob = sob + block->chars;    // Find text block for pos
  781.     
  782.     p = block->text+(pos-sob);        // Start part way through this one
  783.     
  784.     while (block) {
  785.         for(; p < block->text+block->chars; p++) {
  786.             if (found_newline) {
  787.             if (*p != '\n')
  788.             return sob + (p-block->text); /* Position after newline */
  789.         } else {
  790.         if (*p == '\n') {
  791.             found_newline = YES;
  792.         }
  793.         }
  794.     }
  795.     sob = sob + block->chars;    /* Move to next block */
  796.         block = block->next;
  797.     if (block) p = block->text;
  798.     
  799.     }
  800.     return textLength;
  801. }
  802.  
  803.  
  804. //    Do two runs imply the same format?
  805. //    ----------------------------------
  806.  
  807. BOOL run_match(NXRun* r1, NXRun *r2)
  808. {
  809.     return     (r1->font == r2->font)
  810.         &&    (r1->paraStyle == r2->paraStyle)
  811.         &&    (r1->textGray == r2->textGray)
  812.         &&    (r1->textRGBColor == r2->textRGBColor)
  813.     &&    (r1->superscript == r2->superscript)
  814.     &&    (r1->subscript == r2->subscript)
  815.     &&    (r1->info == r2->info)
  816.     &&    (*(int *)&r1->rFlags == *(int*)&r2->rFlags);
  817. }
  818.  
  819. //    Check Consecutive runs and merge if necessary
  820. //    ---------------------------------------------
  821. //
  822. //    If the runs match in EVERY way, they are combined into one, and
  823. //    all the other runs are shuffled down.
  824. //
  825. - (BOOL) mergeRun: (NXRun *) run
  826. {
  827.     NXRun * r, *last;
  828.     if (run_match(run, run+1) ) {
  829.         if (TRACE) printf("HT: Merging run %p\n", run);
  830.         run->chars = run->chars +(run+1)->chars;
  831.     last = ((NXRun *)((char*)theRuns->runs + theRuns->chunk.used))-1;
  832.     for(r=run+1; r<last; r++) r[0] = r[1];
  833.         theRuns->chunk.used = theRuns->chunk.used - sizeof(*r);
  834.         return YES;
  835.     }
  836.     return NO;
  837. }
  838.  
  839.  
  840. //    Apply style to a given region
  841. //    -----------------------------
  842. //
  843. //     Note that one should not have two consecutive runs of the same style,
  844. //     nor any zero length runs. We have a little calculation, therefore,
  845. //     in order to work out how many runs will eventually be needed:
  846. //    this may be more or less than we started with.
  847. //    Remember that appling a style to a run may or may not change it.
  848. //
  849. //    PS: Actually, we notice that text insertion does leave two consecutive
  850. //    runs the same in the Text object, but deletion cleans up.
  851.  
  852.  
  853. - applyStyle:(HTStyle *)style from:(int)start to:(int)end
  854. {
  855.     int pos;                /* Character position within text */
  856.     int increase;            /* Number of runs to be split    */
  857.     int new_used;            /* New number of bytes in runs    */
  858.     BOOL need_run_before, need_run_after;/* Sometimes we don't need them    */
  859.     int    run_before_start, run_after_end;/* Start of run_before etc     */
  860.     NXRun * s, *e;            /* Start and end run         */
  861.     NXRun *p;                /* Pointer to run being read    */
  862.     NXRun *w;                /* Pointer to run being written    */
  863.     NXRun *r;                /* Pointer to end of runs    */
  864.     
  865.     if (start == end) {
  866.         apply(style, &typingRun);        /* Will this work? */
  867.         if (TRACE) printf("Style applied to typing run\n");
  868.     return nil;                /* Can't operate on nothing */
  869.     }
  870.  
  871. //    First we determine in which runs the first and last characters to
  872. //    be changed lie.
  873.    
  874.     for (pos=0, s=theRuns->runs; pos+s->chars<=start;
  875.                 pos = pos+((s++)->chars)) /*loop*/;
  876. /*    s points to run containing char after selection start */
  877.     run_before_start = pos;
  878.  
  879.     for (e=s; pos+e->chars < end; pos = pos+((e++)->chars)) ;    /* Find end run */
  880. /*    e points to run containing character before selection end */
  881.     run_after_end = pos+e->chars;
  882.  
  883.     r = (NXRun *) (((char *)(theRuns->runs)) +theRuns->chunk.used);    /* The end*/
  884.     
  885.     if (TRACE) {
  886.         printf("Runs: used=%i, elt. size=%i, %i elts, total=%i\n",
  887.             theRuns->chunk.used, sizeof(*r), r - theRuns->runs,
  888.         (r-theRuns->runs)*sizeof(*r) );   
  889.     printf("    runs at %i, r=%i. textLength:%i, r ends at:%i\n",
  890.         theRuns->runs, r, textLength, pos);
  891.     }
  892.     
  893.        
  894. //    Move up runs as necessary in order to make room for the splitting
  895. //    of the start and end runs into two.  We only do this if necessary.
  896.  
  897.     if (!willChange(style, s))
  898.         start = run_before_start;    /* No run before is needed now */
  899.     need_run_before = (start>run_before_start);
  900.  
  901.     if (!willChange(style, e)) 
  902.         end = run_after_end;            /* No run after is needed now */
  903.     need_run_after = (end < run_after_end);
  904.  
  905.     if (TRACE) printf(
  906.         "Run s=%i, starts at %i; changing (%i,%i); Run e=%i ends at %i\n",
  907.         s-theRuns->runs, run_before_start, start, end, e-theRuns->runs, run_after_end); 
  908.     
  909.     increase = need_run_after + need_run_before;
  910.     if (increase) {
  911.         new_used = theRuns->chunk.used + increase*sizeof(*r);   
  912.     if (new_used> theRuns->chunk.allocated) {
  913.         NXRun* old = theRuns->runs;
  914.         theRuns = (NXRunArray*)NXChunkGrow(&theRuns->chunk, new_used);
  915.         if (theRuns->runs !=old) {            /* Move pointers */
  916.         if (TRACE) printf("HT:Apply style: moving runs!\n");
  917.         e = theRuns->runs + (e-old);
  918.         r = theRuns->runs + (r-old);
  919.         s = theRuns->runs + (s-old);
  920.         }
  921.     }
  922.     for (p=r-1; p>=e; p--) p[increase] = p[0];    /* Move up the runs after */
  923.     r = r+increase;                    /* Point to after them 910212*/
  924.     /* p = e-1 */
  925.     
  926.     if (need_run_after) {
  927.         e = e + increase-1;                /* Point last to be changed */
  928.         e[0] = e[1];                /* Copy the last run */
  929.         e[1].chars = run_after_end - end;
  930.         e[0].chars = e[0].chars - e[1].chars;    /* Split the run into two */
  931.     }
  932.     
  933.     if (need_run_before) {
  934.         for(;p>=s; p--) p[1] = p[0];        /* Move runs up, copying 1st*/
  935.         s[0].chars = start - run_before_start;    /* Split the run into two */
  936.         if (need_run_after && (s+1==e)) {        /* If only one middle run */
  937.         s[1].chars = end-start;            /* The run we need */
  938.         } else {
  939.             s[1].chars = s[1].chars - s[0].chars;    /* The remainder */
  940.         }
  941.         s++;        /* Move on to point to first run to be changed */
  942.         if (!need_run_after) e++;            /* First to be changed */
  943.     }
  944.         theRuns->chunk.used = new_used; 
  945.     
  946.     } /* end if increase */
  947.     
  948. //    We consider the bit of text which is to be styled, s thru e.
  949. //    We scan through, first, applying the style, until we find two runs which
  950. //    need to be merged.
  951.  
  952.     p=s;
  953.     if (p==theRuns->runs) {
  954.         apply(style, p++);        /* Don't merge with run -1! */
  955.     }
  956.     
  957.     for(; p<=e; p++) {
  958.     apply(style, p);
  959.     if (run_match(p,p-1)) {
  960.         break;
  961.         }
  962.     }
  963.  
  964. //    Once we have merged two runs, we have to copy the rest of them across,
  965. //    merging others as necessary.
  966.  
  967.     w = p-1;            /* w now points to last written run */
  968.     for(;p<=e; p++) {
  969.         apply(style, p);
  970.     if (run_match(p,w)) {
  971.         w->chars = w->chars+p->chars;    /* Combine  with w */
  972.     } else {
  973.         w++;                /* or skip */
  974.         *w = *p;                /* and keep a copy */
  975.     }
  976.     }
  977.  
  978. //    Now, is any runs were merged, we have to copy the rest of the runs down
  979. //    and decrease the size of the chunk.
  980.  
  981.     w++;                    /* Point to next to be written */
  982.     if (w<p) {                    /* If any were moved, */
  983.     for(;p<r;) *w++ = *p++;            /* Move the following runs down */    
  984.         theRuns->chunk.used = (char*)w - (char*)theRuns->runs; 
  985.     }
  986.     
  987.     [self calcLine];                /* Update line breaks */
  988.     return [window display];                /* Update window */
  989. }
  990.  
  991.  
  992. //    Apply a style to the selection
  993. //    ------------------------------
  994. //
  995. //    If the style is a paragraph style, the
  996. //    selection is extended to encompass a number of paragraphs.
  997. //
  998. - applyStyle:(HTStyle *)style
  999. {
  1000.     int start, end;
  1001.     if (TRACE) printf("Applying style %i to (%i,%i)\n",
  1002.             style, sp0.cp, spN.cp);
  1003.     
  1004.     if (sp0.cp<0) {                    /* No selection */
  1005.         return [self applyStyle:style from:0 to:0];    /* Apply to typing run */
  1006.     }
  1007.     
  1008.     if (!style) return nil;
  1009.  
  1010.     if ([self isEditable]) [window setDocEdited:YES];
  1011.     else return nil;
  1012.  
  1013.         
  1014.     start = sp0.cp;
  1015.     end = spN.cp;
  1016.     if (style->paragraph) {    /* Extend to an integral number of paras. */
  1017.         start = [self startOfParagraph:start];
  1018.         end = [self endOfParagraph:end];
  1019.     }
  1020.     return [self applyStyle:style from:start to:end];
  1021. }
  1022.  
  1023.  
  1024. //    Apply style to all similar text
  1025. //    -------------------------------
  1026. //
  1027. //
  1028. - applyToSimilar:(HTStyle*)style
  1029. {
  1030.     NXRun * r = theRuns->runs;
  1031.     int sor;
  1032.     NXRun old_run;
  1033.     
  1034.     for (sor = 0; sor<=sp0.cp; sor = sor+((r++)->chars)) ;/* Find run after */
  1035.     old_run = *(r-1);            /* Point to run for start of selection */
  1036.  
  1037.     if (TRACE) printf(
  1038.         "Applying style %i to unstyled text similar to (%i,%i)\n",
  1039.         style, sp0.cp, spN.cp);
  1040.  
  1041.     for(r=theRuns->runs;
  1042.          (char*)r-(char*)theRuns->runs < theRuns->chunk.used; r++) {
  1043.         if (r->paraStyle == old_run.paraStyle) {
  1044.         if(TRACE) printf("    Applying to run %i\n", r);
  1045.         apply(style,r);
  1046.         if (r!=theRuns->runs){
  1047.             if ([self mergeRun:r-1]) r--;    /* Do again if shuffled down */
  1048.         }
  1049.     }
  1050.     }
  1051.     [self calcLine];
  1052.     [window display];
  1053.     return self;
  1054. }
  1055.  
  1056. //    Pick up the style of the selection
  1057. //    ----------------------------------
  1058.  
  1059. - (HTStyle *) selectionStyle:(HTStyleSheet *) sheet
  1060. {
  1061.     NXRun * r = theRuns->runs;
  1062.     int sor;
  1063.     
  1064.     for (sor = 0; sor<=sp0.cp; sor = sor+((r++)->chars)) ;/* Find run after */
  1065.     r--;                /* Run for start of selection */
  1066.     return HTStyleForRun(sheet, r);    /* for start of selection */
  1067.     
  1068. }
  1069.  
  1070.  
  1071. //    Another replaceSel method, this time using styles:
  1072. //    -------------------------------------------------
  1073. //
  1074. //    The style is as given, or where that is not defined, as the
  1075. //    current style of the selection.
  1076.  
  1077. - replaceSel: (const char *)aString style:(HTStyle*)aStyle
  1078. {
  1079.     NXRun * r = theRuns->runs;
  1080.     int sor;
  1081.     NXRunArray    newRuns;
  1082.     
  1083.     for (sor = 0; sor<=sp0.cp; sor = sor+((r++)->chars)) ;/* Find run after */
  1084.     r--;                    /* Run for start of selection */
  1085.     newRuns.runs[0] = *r;            /* Copy it */
  1086.     newRuns.chunk.used = sizeof(*r);        /* 1 run used */
  1087.     apply(aStyle, newRuns.runs);        /* change it */
  1088.     newRuns.runs->chars = strlen(aString);    /* Match the size to the string */
  1089.     return [self replaceSel:aString length:newRuns.runs->chars runs:&newRuns];
  1090. }
  1091.  
  1092.  
  1093. //    Read in as Plain Text                    readText:
  1094. //    ---------------------
  1095. //
  1096. //    This method overrides the method of Text, so as to force a plain text
  1097. //    hypertext to be monofont and fixed width.  Also, the window is updated.
  1098. //
  1099. - readText: (NXStream *)stream
  1100. {
  1101. //    [self setMonoFont:YES];        Seems to leave it in a strange state
  1102.     [self setHorizResizable:YES];
  1103.     [self setNoWrap];
  1104.     [self setFont:[Font newFont:"Ohlfs" size:10.0]];    // @@ Should be XMP
  1105.     [super readText:stream];
  1106.     format = WWW_PLAINTEXT;                // Remember
  1107.     
  1108. #ifdef NOPE
  1109.     {
  1110.       NXRect frm;        /* Try this to get over "text strangeness" */
  1111.       [self getFrame:&frm];      /* on plain text only Aug 91 */
  1112.       [self renewRuns:NULL text:NULL frame:&frm tag:0];
  1113.     }
  1114. #endif
  1115.     [self adjustWindow];
  1116.     return self;
  1117. }
  1118.  
  1119.  
  1120. //    Read in as Rich Text                    readRichText:
  1121. //    ---------------------
  1122. //
  1123. //    This method overrides the method of Text, so as to force a plain text
  1124. //    hypertext to be monofont and fixed width.  Also, the window is updated.
  1125. //
  1126. - readRichText: (NXStream *)stream
  1127. {
  1128.     id status =  [super readRichText:stream];
  1129.     [self adjustWindow];
  1130.     format = WWW_RICHTEXT;                // Remember
  1131.     return status;
  1132. }
  1133.  
  1134. //                Window Delegate Methods
  1135. //                =======================
  1136.  
  1137. //    Prevent closure of edited window without save
  1138. //
  1139. - windowWillClose:sender
  1140. {
  1141.     int choice;
  1142.     if (![window isDocEdited]) return self;
  1143.     choice = NXRunAlertPanel("Close", "Save changes to `%s'?",
  1144.         "Yes", "No", "Don't close", [window title]);
  1145.     if (choice == NX_ALERTALTERNATE) return self;
  1146.     if (choice == NX_ALERTOTHER) return nil;
  1147.  
  1148.     return nil; //[server saveNode:self];    @@@@@@@@ server!!!
  1149. }
  1150.  
  1151. //    Change configuration as window becomes key window
  1152. //
  1153. - windowDidBecomeMain:sender
  1154. {
  1155.     return [delegate hyperTextDidBecomeMain:self];
  1156. }
  1157.  
  1158. /*                FORMAT CONVERSION FROM SGML
  1159. **                ===========================
  1160. **
  1161. **    As much as possible, this is written in C for portability. It is in a separate
  1162. **    include file which could be used elsewhere.
  1163. */
  1164. /*        Input procedure for printing a trace as we go
  1165. */
  1166. /*#define NEXT_CHAR NXGetc(sgmlStream)
  1167.   #define BACK_UP NXUngetc(sgmlStream)
  1168. */
  1169. /*    Globals for using many subroutines within a method
  1170. */
  1171. static HyperText * HT;                /* Pointer to self for C */
  1172.  
  1173. //    Inputting from the text object:
  1174. //    ------------------------------
  1175.  
  1176. static unsigned char * read_pointer;        /* next character to be read */
  1177. static unsigned char * read_limit;
  1178. static NXTextBlock * read_block;
  1179.  
  1180. void start_input()
  1181. {
  1182.     read_block = HT->firstTextBlock;
  1183.     read_pointer = read_block->text;    /* next character to be read */
  1184.     read_limit = read_pointer+read_block->chars;
  1185. }
  1186.  
  1187. unsigned char next_input_block()
  1188. {
  1189.     char c = *read_pointer;
  1190.     read_block = read_block->next;
  1191.     if (!read_block) read_block = HT->firstTextBlock;    /* @@@ FUDGE */
  1192.     read_pointer = read_block->text;
  1193.     read_limit = read_pointer + read_block->chars;
  1194.     return c;
  1195. }
  1196. #define START_INPUT start_input()
  1197. #define NEXT_TEXT_CHAR (read_pointer+1==read_limit? next_input_block():*read_pointer++)
  1198.  
  1199.  
  1200.  
  1201.  
  1202. //            Outputting to the text object
  1203. //            =============================
  1204. //
  1205. //    These macros are used by the parse routines
  1206. //
  1207. #define BLOCK_SIZE NX_TEXTPER            /* Match what Text seems to use */
  1208.  
  1209. static NXTextBlock     *write_block;        /* Pointer to block being filled */
  1210. static unsigned char     *write_pointer;    /* Pointer to next characetr to be written */
  1211. static unsigned char    *write_limit;    /* Pointer to the end of the allocated area*/
  1212. static NXRun *        lastRun;    /* Pointer to the run being appended to */
  1213. static int        original_length; /* of text */
  1214. static HTStyle * appending_style = 0;
  1215.  
  1216. void end_output();    /* Forward */
  1217.  
  1218. #define OUTPUT(c)    { *write_pointer++ = (c); \
  1219.     if (write_pointer==write_limit) {end_output(); append_start_block(); }}
  1220. #define OUTPUTS(string)    {const char * p; for(p=(string);*p;p++) OUTPUT(*p);}
  1221. #define START_OUTPUT    append_begin()
  1222. #define FINISH_OUTPUT    finish_output()
  1223. #define LOADPLAINTEXT    loadPlainText()
  1224. #define SET_STYLE(s) set_style(s)
  1225.  
  1226.  
  1227. //    Allocate a text block to accumulate text
  1228. //
  1229. // Bugs:
  1230. // It might seem logical to set the "malloced" bit to 1, because the text block
  1231. // has been allocted with malloc(). However, this crashes the program as at the
  1232. // next edit of the text, the text object frees the block while still using it.
  1233. // Chaos results, sometimes corrupting the stack and/or looping for ages. @@
  1234. // We therefore set it to zero! (This might have been something else -TBL)
  1235. //
  1236. void append_start_block()
  1237. {    
  1238.     NXTextBlock *previous_block=write_block;    /* to previous write block */
  1239.     
  1240.     if (TRACE)printf("    Starting to append new block.\n");
  1241.         
  1242.     lastRun = ((NXRun*) ((char*)HT->theRuns->runs +
  1243.                     HT->theRuns->chunk.used))-1;
  1244.     write_block = (NXTextBlock*)malloc(sizeof(*write_block));
  1245.     write_block->tbFlags.malloced=0;        /* See comment above */
  1246.     write_block->text = (unsigned char *)malloc(BLOCK_SIZE);
  1247.     write_block->chars = 0;            // For completeness: not used.
  1248.     write_pointer = write_block->text;
  1249.     write_limit = write_pointer + BLOCK_SIZE;
  1250.  
  1251. //    Add the block into the linked list after previous block:
  1252.  
  1253.     write_block->prior = previous_block;
  1254.     write_block->next = previous_block->next;
  1255.     if (write_block->next) write_block->next->prior = write_block;
  1256.         else HT->lastTextBlock = write_block;
  1257.     previous_block->next = write_block;
  1258.      
  1259.  
  1260. }
  1261.  
  1262. //     Start the output process altogether
  1263. //
  1264. void append_begin()
  1265. {
  1266.     if (TRACE)printf("Begin append to text.\n");
  1267.     appending_style = NULL;        /* Bug @@@@ */
  1268.     
  1269.     [HT setText:""];                // Delete everything there
  1270.     original_length = HT->textLength; 
  1271.     if (TRACE) printf("Text now contains %i characters\n", original_length);
  1272.  
  1273.         
  1274.     lastRun = ((NXRun*) ((char*)HT->theRuns->runs +
  1275.                 HT->theRuns->chunk.used))-1;
  1276.  
  1277. //    Use the last existing text block:
  1278.  
  1279.     write_block = HT->lastTextBlock;
  1280.     
  1281. //    It seems that the Text object doesn't like to be empty: it always wants to
  1282. //    have a newline in at least. However, we need it seriously empty and so we
  1283. //    forcible empty it. CalcLine will crash if called with it in this state.
  1284.  
  1285.     if (original_length==1) {
  1286.         if (TRACE) printf("HT: Clearing out single character from Text.\n");
  1287.         lastRun->chars =  0;        /* Empty the run */
  1288.     write_block->chars = 0;        /* Empty the text block */
  1289.     HT->textLength = 0;        /* Empty the whole Text object */
  1290.     original_length = 0;        /* Note we have cleared it */
  1291.     }
  1292.  
  1293.     write_pointer = write_block->text+write_block->chars;
  1294.     write_limit = write_pointer + BLOCK_SIZE;
  1295. }
  1296.  
  1297.  
  1298.  
  1299. //    Insert vertical white space
  1300. //
  1301. void paragraph_gap(HTStyle * next)
  1302. {
  1303.     if ((next->paragraph)
  1304.       && appending_style
  1305.       && (appending_style->paragraph != next->paragraph)
  1306.       && next) {
  1307.         int i;
  1308.     float space = appending_style->spaceAfter > next->spaceBefore ?
  1309.             appending_style->spaceAfter : next->spaceBefore;    /* max */
  1310.         int newlines = (space/appending_style->paragraph->lineHt) + 1;
  1311.     
  1312.         for(i=0; i<newlines; i++) OUTPUT('\n');    /* Rather approximate!     */
  1313.     }
  1314. }
  1315.  
  1316.  
  1317. //    Set a style for new text
  1318. //
  1319. void set_style(HTStyle *style)
  1320. {
  1321.     if (!style) {
  1322.         if (TRACE) printf("set_style: style is null!\n");
  1323.     return;
  1324.     }
  1325.     if (TRACE) printf("    Changing to style `%s' -- %s change.\n",
  1326.         style->name, willChange(style, lastRun) ? "will" : "won't");
  1327.     if (willChange(style, lastRun)) {
  1328.         int size;
  1329.     paragraph_gap(style);
  1330.     size = (write_pointer - write_block->text);
  1331.     lastRun->chars = lastRun->chars + size - write_block->chars;
  1332.     write_block->chars = size;
  1333.         if (lastRun->chars) {
  1334.         int new_used = (((char *)(lastRun+2)) - (char*)HT->theRuns->runs);
  1335.         if (new_used > HT->theRuns->chunk.allocated) {
  1336.             if (TRACE) printf("    HT: Extending runs.\n"); 
  1337.         HT->theRuns = (NXRunArray*)NXChunkGrow(
  1338.             &HT->theRuns->chunk, new_used);
  1339.         lastRun = ((NXRun*) ((char*)HT->theRuns->runs +
  1340.                      HT->theRuns->chunk.used))-1;
  1341.         }
  1342.         lastRun[1]=lastRun[0];
  1343.         lastRun++;
  1344.         HT->theRuns->chunk.used = new_used; 
  1345.     }
  1346.     apply(style, lastRun);
  1347.     lastRun->chars = 0;        /* For now */
  1348.     }
  1349.     if (style->paragraph) appending_style = style;
  1350. }
  1351.  
  1352.  
  1353. //    Transfer text to date to the Text object
  1354. //    ----------------------------------------
  1355. void end_output()
  1356. {
  1357.     int size = (write_pointer - write_block->text);
  1358.     if (TRACE)printf(
  1359.         "    HT: Adding block of %i characters, starts: `%.20s...'\n",
  1360.         size, write_block->text);
  1361.     lastRun->chars = lastRun->chars + size - write_block->chars;
  1362.     write_block->chars = size;
  1363.     HT->textLength = HT->textLength + size;
  1364.  
  1365. }
  1366.  
  1367.  
  1368. //    Finish altogether
  1369. //    -----------------
  1370.  
  1371. void finish_output()
  1372. {
  1373.     int size;
  1374.     if (write_pointer == write_block->text && !write_block->prior) {
  1375.         OUTPUT('\n');    /* Avoid empty object */
  1376.     }
  1377.     size = write_pointer - write_block->text;
  1378.     if (size==0) {
  1379.         if (write_block->prior) {    /* If there are other blocks */
  1380.         HT->lastTextBlock = write_block->prior;/* Remove empty text block */
  1381.         write_block->prior->next = 0;
  1382.         free(write_block->text);
  1383.         free(write_block);
  1384.     }
  1385.     } else {
  1386.     end_output();
  1387.     }
  1388.  
  1389. // get rid of zero length run if any
  1390.  
  1391.     if (lastRun->chars==0) {        /* Chop off last run */
  1392.         HT->theRuns->chunk.used= (char*)lastRun - (char*)HT->theRuns;
  1393.     }
  1394.     
  1395. //    calcLine requires that the last character be a newline!
  1396.     {
  1397.         unsigned char * p = HT->lastTextBlock->text +
  1398.                 HT->lastTextBlock->chars - 1;
  1399.     if (*p != '\n') {
  1400.         if (TRACE)
  1401.         printf(
  1402.         "HT: Warning: Last character was %i not newline: overwriting!\n",
  1403.             *p);
  1404.         *p = '\n';
  1405.     }
  1406.     }
  1407.  
  1408.     [HT adjustWindow];            /* Adjustscrollers and window size */
  1409.  
  1410. }
  1411.  
  1412.  
  1413. //    Methods enabling an external parser to add styled data
  1414. //    ------------------------------------------------------
  1415. //
  1416. //    These use the macros above in the same way as a built-in parser would.
  1417. //
  1418. - appendBegin
  1419. {
  1420.     HT = self;
  1421.     START_OUTPUT;
  1422.     return self;
  1423. }
  1424.  
  1425. - appendStyle:(HTStyle *) style
  1426. {
  1427.     SET_STYLE(style);
  1428.     return self;
  1429. }
  1430. - appendText: (const char *)text
  1431. {
  1432.     OUTPUTS(text);
  1433.     return self;
  1434. }
  1435.  
  1436. /*    Append character takes care of white space for paragraph marks.
  1437. */
  1438. - appendCharacter: (char)ch
  1439. {
  1440.     if ((ch=='\n') && appending_style && appending_style->paragraph) {
  1441.  
  1442.     int newlines = (
  1443.       ((appending_style->spaceBefore > appending_style->spaceAfter) ?
  1444.            appending_style->spaceBefore : appending_style->spaceAfter) /
  1445.                 appending_style->paragraph->lineHt)         + 1;
  1446.     int i;
  1447.     for(i=0; i<newlines; i++) OUTPUT('\n');    /* approximate! @@    */
  1448. /*    OUTPUTS(appending_style->paragraph_text);  */
  1449.  
  1450.     } else OUTPUT(ch);
  1451.     return self;
  1452. }
  1453.  
  1454. - appendEnd
  1455. {
  1456.     FINISH_OUTPUT;
  1457.     return self;
  1458. }
  1459.  
  1460. //    Begin an anchor
  1461.  
  1462. - (HTChildAnchor *)appendBeginAnchor: (HTChildAnchor *) a;
  1463. {
  1464.     HTStyle * style = HTStyleNew();
  1465.  
  1466.     style->anchor = a;
  1467.     HTAnchor_makeLastChild((HTChildAnchor *)style->anchor); /* Put in correct order */          
  1468.     SET_STYLE(style);        /* Start anchor here */
  1469.     free(style);
  1470.     return a;
  1471. }
  1472.  
  1473.  
  1474. - appendEndAnchor            // End it
  1475. {
  1476.     HTStyle * style = HTStyleNew();
  1477.     style->anchor = CLEAR_POINTER;
  1478.     SET_STYLE(style);        /* End anchor here */
  1479.     free(style);
  1480.     return self;
  1481. }
  1482.  
  1483. //    Reading from a NeXT stream
  1484. //    --------------------------
  1485.  
  1486. #ifdef OLD_CODE
  1487. #define END_OF_FILE    NXAtEOS(sgmlStream)
  1488. #define NEXT_CHAR    NXGetc(sgmlStream)
  1489. #define BACK_UP        NXUngetc(sgmlStream)
  1490. #endif
  1491.  
  1492. #include "ParseHTML.h"
  1493.  
  1494.  
  1495.  
  1496. //            Methods overriding Text methods
  1497. //            ===============================
  1498. //
  1499. //    Respond to mouse events
  1500. //    -----------------------
  1501. //
  1502. // The first click will have set the selection point.  On the second click,
  1503. // we follow a link if possible, otherwise we allow Text to select a word as usual.
  1504. //
  1505. - mouseDown:(NXEvent*)theEvent
  1506. {
  1507.     if (theEvent->data.mouse.click != 2) return [super mouseDown:theEvent];
  1508.     if (![self followLink]) return [super mouseDown:theEvent];
  1509.     return self;
  1510. }
  1511.  
  1512.  
  1513. //    The following are necessary to undo damage done by the Text object
  1514. //    in version 2.0 of NeXTStep. For some reason, iff the "info" is
  1515. //    nonzero, text typed in is given
  1516. //    a different copy of the typingRun parastyle, and zero anchor info.
  1517. //    Otherwise, the current run is broken in two at the insertion point,
  1518. //    but no changes made to the run contents.
  1519. //    The problem with simply repairing is that many runs will be made inside
  1520. //    an anchor.
  1521. //    We have to use a "dummy" flag to mean "This has an anchor: be careful!"
  1522. //    This is horrible.
  1523.  
  1524. - keyDown:(NXEvent*)theEvent
  1525. #ifdef TRY1
  1526. {
  1527.     id result;
  1528.     NXTextStyle *typingPara = typingRun.paraStyle;
  1529.     int originalLength = textLength;
  1530.     int originalStart = sp0.cp;
  1531.     int originalEnd = spN.cp;
  1532.     result = [super keyDown:theEvent];
  1533.     
  1534.     {
  1535.     int inserted = originalEnd-originalStart +
  1536.             textLength-originalLength;
  1537.     
  1538.     if (TRACE) printf(
  1539.     "KeyDown, size(sel) %i (%i-%i)before, %i (%i-%i)after.\n",
  1540.         originalLength, originalStart, originalEnd,
  1541.         textLength, sp0.cp, spN.cp);
  1542.         
  1543.     if (inserted>0) {
  1544.         NXRun * s;
  1545.         int pos;
  1546.         int start = sp0.cp-inserted;
  1547.         for (pos=0, s=theRuns->runs; pos+s->chars<=start;
  1548.             pos = pos+((s++)->chars)) /*loop*/;
  1549.  
  1550. //    s points to run containing first char of insertion
  1551.  
  1552.         if (pos!=start)
  1553.             printf(
  1554.         "HT: Strange: inserted %i at %i, start of run=%i !!\n",
  1555.                     inserted, start, pos);
  1556.                     
  1557.         if (s > theRuns->runs) {    /* ie s-1 is valid */
  1558.         s->paraStyle = typingPara;    /* Repair damage to runs */
  1559.         /* What about freeing the old paragraph style? @@ */
  1560.         s->info = (s-1)->info;
  1561.         s->rFlags.dummy = 1;    /* Pass on flag */
  1562.         }
  1563.         
  1564.     }
  1565.     }
  1566.     return result;
  1567. }
  1568. #else
  1569. //    The typingRun field does not seem to reliably reflect the
  1570. //    format which would be appropriate if typing were to occur.
  1571. //    We have to use our own.
  1572. {
  1573.     NXRun run;
  1574.     {
  1575.     NXRun * s;    /* To point to run BEFORE selection */
  1576.     int pos;
  1577.  
  1578. /*     If there is a nonzero selection, take the run containing the
  1579. **    first character. If the selection is empty, take the run containing the
  1580. **    character before the selection.
  1581. */
  1582.     if (sp0.cp == spN.cp) {
  1583.         for (pos=0, s=theRuns->runs; pos+s->chars<sp0.cp;    /* Before */
  1584.         pos = pos+((s++)->chars)) /*loop*/;
  1585.     } else {
  1586.         for (pos=0, s=theRuns->runs; pos+s->chars<=sp0.cp;    /* First ch */
  1587.         pos = pos+((s++)->chars)) /*loop*/;
  1588.     }
  1589.  
  1590. /*    Check our understanding */
  1591.  
  1592.     if (typingRun.paraStyle != 0) {
  1593.         if  (typingRun.paraStyle != s->paraStyle)
  1594.             printf("WWW: Strange: Typing run has bad style.\n");
  1595.         if ((s->info != 0)
  1596.             && (typingRun.info != s->info))
  1597.             printf(
  1598.         "WWW: Strange: Typing run has bad anchor info.\n");
  1599.     }
  1600.     
  1601.     typingRun = *s;        /* Copy run to be used for insertion */
  1602.     run = *s;        /* save a copy */
  1603.     }
  1604.     
  1605.     if (!run.rFlags.dummy) return [super keyDown:theEvent]; // OK!
  1606.  
  1607.     {
  1608.     id result;
  1609.     int originalLength = textLength;
  1610.     int originalStart = sp0.cp;
  1611.     int originalEnd = spN.cp;
  1612.     result = [super keyDown:theEvent];
  1613.     
  1614. /*     Does it really change? YES!
  1615. */    
  1616.     if (TRACE) {
  1617.         if (typingRun.info != run.info) printf(
  1618.         "Typing run info was %p, now %p !!\n",
  1619.         run.info, typingRun.info);
  1620.         if (typingRun.paraStyle != run.paraStyle) printf(
  1621.         "Typing run paraStyle was %p, now %p !!\n",
  1622.         run.paraStyle, typingRun.paraStyle);
  1623.     }
  1624. /*    Patch the new run if necessary:
  1625. */
  1626.     {
  1627.         int inserted = originalEnd-originalStart +
  1628.                  textLength-originalLength;
  1629.         
  1630.         if (TRACE) printf(
  1631.     "KeyDown, size(sel) %i (%i-%i)before, %i (%i-%i)after.\n",
  1632.         originalLength, originalStart, originalEnd,
  1633.         textLength, sp0.cp, spN.cp);
  1634.  
  1635.         if (inserted>0) {
  1636.             NXRun * s;
  1637.         int pos;
  1638.         int start = sp0.cp-inserted;
  1639.             for (pos=0, s=theRuns->runs; pos+s->chars<=start;
  1640.                 pos = pos+((s++)->chars)) /*loop*/;
  1641.  
  1642. //    s points to run containing first char of insertion
  1643.  
  1644.         if (pos!=start) {    /* insert in middle of run */
  1645.             if (TRACE) printf(
  1646.             "HT: Inserted %i at %i, in run starting at=%i\n",
  1647.                     inserted, start, pos);
  1648.                     
  1649.         } else {    /* inserted stuff starts run */
  1650.             if (TRACE) printf ("Patching info from %d to %d\n",
  1651.                 s->info, run.info);
  1652.                 s->info = run.info;
  1653.             s->paraStyle = run.paraStyle;    /* free old one? */
  1654.             s->rFlags.dummy = 1;
  1655.         }
  1656.         } /* if inserted>0 */
  1657.  
  1658.     } /* block */
  1659.     return result;
  1660.     }
  1661. }
  1662. #endif
  1663. //    After paste, determine paragraph styles for pasted material:
  1664. //    ------------------------------------------------------------
  1665.  
  1666. - paste:sender;
  1667. {
  1668.     id result;
  1669.     int originalLength = textLength;
  1670.     int originalStart = sp0.cp;
  1671.     int originalEnd = spN.cp;
  1672.     HTChildAnchor * typingInfo;
  1673.     
  1674.     result = [super paste:sender];        // Do the paste
  1675.         
  1676.     {
  1677.     int inserted = originalEnd-originalStart +
  1678.             textLength-originalLength;
  1679.     
  1680.     if (TRACE) printf(
  1681.     "Paste, size(sel) %i (%i-%i)before, %i (%i-%i)after.\n",
  1682.         originalLength, originalStart, originalEnd,
  1683.         textLength, sp0.cp, spN.cp);
  1684.         
  1685.     if (inserted>0) {
  1686.         NXRun *s, *r;
  1687.         int pos;
  1688.         int start = sp0.cp-inserted;
  1689.         for (pos=0, s=theRuns->runs; pos+s->chars<=start;
  1690.             pos = pos+((s++)->chars)) /*loop*/;
  1691. //        s points to run containing first char of insertion
  1692.  
  1693.         if (pos!=sp0.cp-inserted)
  1694.             printf("HT paste: Strange: insert@%i != run@%i !!\n",
  1695.                     start, pos);
  1696.                     
  1697.         if (s > theRuns->runs) typingInfo = (s-1)->info;
  1698.         else typingInfo = 0;
  1699.         
  1700.         for (r=s; pos+r->chars<sp0.cp; pos=pos+(r++)->chars) {
  1701.             r->paraStyle = HTStyleForRun(styleSheet, r)->paragraph;
  1702.         r->info = typingInfo;
  1703.         }
  1704.         
  1705.     }
  1706.     }
  1707.     
  1708.     return result;
  1709. }
  1710.  
  1711.  
  1712. @end
  1713.